package cgl.narada.event.impl;

/** This class encapsulates various source and broker defined properties.
    EventProperties facilitates

    (a) The creation of both mutable and immutable properties. Immutable
    properties are never allowed to be changed after they are once set. 
    Mutable properties can be set several times.
    (b) Allows for the inspection of the original source properties
    (c) Allows for the inspection of intermediary properties. These properties
    could be set by the brokers or by other services.

    Note that:
    1. Immutable properties can not be modified after they have once been set
    2. You can track property changes only on immutable properties.
    3. You can not only track changes, but also the entities that initiated
    the changes on these properties.
    
    @author Shrideep Pallickara
    $Date$
    $Revision$
*/

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import cgl.narada.event.EventProperties;
import cgl.narada.event.NBEventException;
import cgl.narada.util.ObjectMarshaller;


public class EventPropertiesImpl implements EventProperties {
  
  private Hashtable properties;
  private Hashtable propertyType;
  private Hashtable propertyModifiers;

  private Hashtable changeTrackerValues;
  private Hashtable changeTrackerModifiers;
  
  private boolean isModified = true;
  private String moduleName = "EventProperties: ";


  protected EventPropertiesImpl() {
    properties = new Hashtable();
    propertyType = new Hashtable();
    propertyModifiers = new Hashtable();
  }
  
  
  /** Check if there are any properties associated with the event */
  public boolean hasProperties() {
    if (properties.size() > 0) {
      return true;
    }
    return false;
  }
  

  
  /** Retrieve the list of property names */
  public Enumeration getPropertyNames() {
    if (properties.size() == 0) {
      return null;
    }

    return properties.keys();
  }
  

  /** Retrieve the list of property names with change tracking enabled. Note
      that this just returns the list of mutable properties that have change
      tracking enabled for them. */
  public Enumeration getPropertyNamesWithChangeTracking() {
    if (changeTrackerValues == null) {
      return null;
    }
    return changeTrackerValues.keys();
  }
  
  
  /** Retrieve the value associated with a property. This method throws an
      exception if 
      <br>(a) This propertyName is not a valid one.
      <br>(b) The specified propertyName is NULL */
  public Object getProperty(Object propertyName) 
    throws NBEventException {
    checkPropertyName(propertyName);
    
    return properties.get(propertyName);
  }
  
  
  
  /** Set a mutable property. This method throws an exception if 
      <br>(a) This property has been previously defined as a mutable property.
      <br>(b) Also neither the property or the property name can be NULL. */
  public void setMutableProperty(Object propertyName, Object property,
				 Object modifier) 
    throws NBEventException {
    
    checkPropertyAddition(propertyName, property, modifier);
    
    if (propertyModifiers.containsKey(propertyName)) {
      propertyModifiers.remove(propertyName);
    }
    isModified = true;
    propertyModifiers.put(propertyName, modifier);

   
    if (!propertyType.containsKey(propertyName)) {
      propertyType.put(propertyName, "mutable");
    }

    properties.put(propertyName, property);
    
    if (changeTrackerValues == null) {
      return;
    }

    if (changeTrackerValues.contains(propertyName)) {
      Vector _values = (Vector) changeTrackerValues.get(propertyName);
      Vector _modifiers = (Vector) changeTrackerModifiers.get(propertyName);
      
      _values.add(property);
      _modifiers.add(modifier);
    }
    
  }
  
  
  
  /** Set an immutable property. This method throws an exception if 
      <br>(a) This immutable property already exists.  
      <br>(b) Also neither the property or the property name can be NULL. */
  public void setImmutableProperty(Object propertyName, Object property, 
				   Object creator)
    throws NBEventException {
    
    checkPropertyAddition(propertyName, property, creator);
    propertyModifiers.put(propertyName, creator);
    propertyType.put(propertyName, "immutable");
    
    properties.put(propertyName, property);
    isModified = true;
  }
  
  
  
  /** Check to see if this is a mutable property. This method throws an
      exception if 
      <br>(a) This property does not exist
      <br>(a) The specified propetyName is NULL  */
  public boolean isMutable(Object propertyName) throws NBEventException {
    checkPropertyName(propertyName);

    String type = (String) propertyType.get(propertyName);
    if (type == null) {
      throw new NBEventException(moduleName + "Property Type for property [" +
				 propertyName + "] not available ");
    }
    
    if (type.equals("mutable")) {
      return true;
    }

    return false;
  }
  
  
  /** Get the last modifier of a certain property. This method works with both
      mutable and immutable properties. This method throws an
      exception if 
      <br>(a) This property does not exist
      <br>(a) The specified propetyName is NULL  */
  public Object getLastModifier(Object propertyName) throws NBEventException {
    checkPropertyName(propertyName);
    
    return propertyModifiers.get(propertyName);
  }
  
  
  /** Track the changes to a specific property. This method throws an 
      exception if 
      <br>(a) This is an immutable property 
      <br>(b) If this property does not exist. 
      <br>(c) The specified propertyName is NULL  */
  public void trackPropertyChanges(Object propertyName)
    throws NBEventException {

    boolean mutable = isMutable(propertyName);
    
    if (!mutable) {
      throw new NBEventException(moduleName + "The specified property is not" +
				 "mutable.");
    }
    
    if (changeTrackerValues == null) {
      changeTrackerValues = new Hashtable();
      changeTrackerModifiers = new Hashtable();
    }
    
    if (!changeTrackerValues.containsKey(propertyName)) {
      changeTrackerValues.put(propertyName, new Vector());
      changeTrackerModifiers.put(propertyName, new Vector());
    }
  }
  
  

  /** Indicates if property change tracking has been enabled for the 
      property in question. This method throws an exception if
      <br>(a) This property does not exist 
      <br>(b) The specified propertyName is not NULL */
  public boolean hasPropertyChangeTracking(Object propertyName)
    throws NBEventException {
    boolean mutable = isMutable(propertyName);
    
    if (!mutable) {
      return false;
    }

    if (changeTrackerValues.containsKey(propertyName)) {
      return true;
    }

    return false;
  }
  


  /** Get a list of the changes associated with a mutable property. This 
      method throws an exception if 
      <br>(a) changeTracking was not enabled on this property 
      <br>(b) The specified property does not exist
      <br>(c) The specified propertyName is NULL  */
  public Enumeration getPropertyChanges(Object propertyName)
    throws NBEventException {
    checkPropertyChangeTracking(propertyName);

    Vector _values = (Vector)changeTrackerValues.get(propertyName);
    return _values.elements();
   
  }
  
  
  /** Gets a list of modifiers on a property. This method throws an exception 
      if 
      <br>(a) changeTracking was not enabled on this property 
      <br>(b) The specified property does not exist
      <br>(c) The specified propertyName is NULL  */
  public Enumeration getPropertyModifiers(Object propertyName) 
    throws NBEventException {
    
    checkPropertyChangeTracking(propertyName);

    Vector _modifiers = (Vector)changeTrackerModifiers.get(propertyName);
    return _modifiers.elements();
    
  }
  
  
  
  /* This method throws an exception if 
     <br>(a) This propertyName is not a valid one.
     <br>(b) The specified propertyName is NULL */
  private void checkPropertyName(Object propertyName) 
    throws NBEventException {
    if (propertyName == null) {
      throw new NBEventException(moduleName + 
				 "Specified propertyName is NULL");
    }

    if (!properties.containsKey(propertyName)) {
      throw new NBEventException(moduleName + "The specified property [" +
				 propertyName + "] does not exist.");
    }
  }

  /*This method throws an exception if 
    <br>(a) This property has been previously defined as a mutable property.
    <br>(b) Also neither the property or the property name can be NULL. 
    <br>(c) The modifier is NULL*/
  private void checkPropertyAddition(Object propertyName, Object property,
				     Object modifier) 
    throws NBEventException {
    if (propertyName == null) {
      throw new NBEventException(moduleName + 
				 "The specified property is NULL");
    }
    
    if (property == null) {
      throw new NBEventException(moduleName + 
				 "The specified preoperty is NULL");
    }
    
    if (modifier == null) {
      throw new NBEventException(moduleName + 
				 "The specified modifier for property [" +
				 property + "] is NULL");
    }
    
    
    if (properties.containsKey(propertyName)) {
      String indicator = (String) propertyType.get(propertyName);
      if (indicator == null) {
	throw new NBEventException(moduleName + "The property [" + 
				   propertyName + "] was not registered as" +
				   " either mutable or immutable.");
      }
      
      if (indicator.equals("immutable")) {
	throw new NBEventException(moduleName + "The property [" +
				   propertyName + "] was previously " + 
				   "registered as an immutable property.");
      }    
    }/* end if properties.containsKey(propertyName) */    
  }


  private void checkPropertyChangeTracking(Object propertyName) 
    throws NBEventException {
     boolean hasChangeTracking = hasPropertyChangeTracking(propertyName);

    if (!hasChangeTracking) {
      throw new NBEventException(moduleName + "This property [" + propertyName 
				 + "] was not previously registered for " +
				 "change tracking");
    }
  }

  /** Get a byte stream representation of the event header*/
  public byte[] getBytes() {
    ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream(50);
    DataOutputStream dout = new DataOutputStream(baOutputStream);
    byte[] marshalledBytes= null;
    
    try {
      ObjectMarshaller.writeObject(properties, dout);
      ObjectMarshaller.writeObject(propertyType, dout);
      ObjectMarshaller.writeObject(propertyModifiers, dout);
      
      if (changeTrackerValues == null) {
	dout.writeBoolean(false); 
      } else {
	dout.writeBoolean(true);
	ObjectMarshaller.writeObject(changeTrackerValues, dout);
	ObjectMarshaller.writeObject(changeTrackerModifiers, dout);
      }/*end if */
      
      marshalledBytes = baOutputStream.toByteArray();
      
    } catch (IOException ioe) {
      System.out.println(moduleName + "Error while marshalling " + ioe);
    } /** end try-catch block*/
    
    return marshalledBytes;
  }

  

  protected EventPropertiesImpl(byte[] propertyBytes) {
    ByteArrayInputStream baInputStream = 
      new ByteArrayInputStream(propertyBytes);
    DataInputStream din = new DataInputStream(baInputStream);
    try {
      
      properties = (Hashtable) ObjectMarshaller.readObject(din);
      propertyType = (Hashtable) ObjectMarshaller.readObject(din);
      propertyModifiers = (Hashtable) ObjectMarshaller.readObject(din);

      boolean hasChangeTracker = din.readBoolean();
      if (hasChangeTracker) {
	changeTrackerValues = (Hashtable) ObjectMarshaller.readObject(din);
	changeTrackerModifiers = (Hashtable) ObjectMarshaller.readObject(din);
      }
      
      isModified = false;
      
    } catch (ClassNotFoundException cnfe) {
      System.out.println(moduleName + "Error while un-marshalling " + cnfe); 
    } catch (IOException ioe) {
      System.out.println(moduleName + "Error while un-marshalling " + ioe);
    } /** end try-catch block*/
  }
  
  
  public String toString() {
    String stringRep = moduleName;
    int numOfProperties = properties.size();
    if (numOfProperties == 0) {
      stringRep += "No properties for this object";
      return stringRep;
    }
    
    stringRep += ("There are (" + numOfProperties +") properties listed.\n" +
		  properties );
    
    return stringRep;
  }
  
  
  public boolean isModified() {
     return isModified;
  }
}
